#include "adalog/Logger.h"
#include "adalog/LogAppender.h"
#include "adalog/LogEvent.h"
#include "adalog/LogFormatter.h"
#include "adalog/LogLevel.h"
#include "adalog/appender/StdOutAppender.h"

namespace adalog
{
    Logger::Logger(const std::string& logger_name,
                   LogLevel::Value log_level,
                   std::list<LogAppender::Ptr> appenders,
                   LogFormatter::Ptr formatter)
        : logger_name_(logger_name)
        , log_level_(log_level)
        , appenders_(appenders.begin(), appenders.end())
        , formatter_(formatter)
    {
    }

    std::string Logger::GetLoggerName() const
    {
        return logger_name_;
    }

    LogLevel::Value Logger::GetLogLevel() const
    {
        return log_level_;
    }

    void Logger::AddAppender(LogAppender::Ptr appender)
    {
        appenders_.push_back(appender);
    }

    void Logger::Log(LogLevel::Value log_level, LogEvent::Ptr event)
    {
        if (log_level < log_level_)
            return;
        std::string log_event = formatter_->Format(event);
        for (auto& appender : appenders_)
        {
            appender->Append(log_event.data(), log_event.size());
        }
    }

#if __cplusplus >= 202002L
    void Logger::Debug(const std::string& payload, std::source_location src_loc)
    {
        auto event = 
            std::make_shared<adalog::LogEvent>(payload, 
                                               logger_name_, 
                                               adalog::LogLevel::DEBUG, 
                                               std::chrono::system_clock::now(), 
                                               src_loc.file_name(), src_loc.line(), 
                                               std::this_thread::get_id());
        Log(event->GetLogLevel(), event);
    }

    void Logger::Info(const std::string& payload, std::source_location src_loc)
    {
        auto event = 
            std::make_shared<adalog::LogEvent>(payload, 
                                               logger_name_, 
                                               adalog::LogLevel::INFO, 
                                               std::chrono::system_clock::now(), 
                                               src_loc.file_name(), src_loc.line(), 
                                               std::this_thread::get_id());
        Log(event->GetLogLevel(), event);
    }

    void Logger::Warn(const std::string& payload, std::source_location src_loc)
    {
        auto event = 
            std::make_shared<adalog::LogEvent>(payload, 
                                               logger_name_, 
                                               adalog::LogLevel::WARN, 
                                               std::chrono::system_clock::now(), 
                                               src_loc.file_name(), src_loc.line(), 
                                               std::this_thread::get_id());
        Log(event->GetLogLevel(), event);
    }

    void Logger::Error(const std::string& payload, std::source_location src_loc)
    {
        auto event = 
            std::make_shared<adalog::LogEvent>(payload, 
                                               logger_name_, 
                                               adalog::LogLevel::ERROR, 
                                               std::chrono::system_clock::now(), 
                                               src_loc.file_name(), src_loc.line(), 
                                               std::this_thread::get_id());
        Log(event->GetLogLevel(), event);
    }

    void Logger::Fatal(const std::string& payload, std::source_location src_loc)
    {
        auto event = 
            std::make_shared<adalog::LogEvent>(payload, 
                                               logger_name_, 
                                               adalog::LogLevel::FATAL, 
                                               std::chrono::system_clock::now(), 
                                               src_loc.file_name(), src_loc.line(), 
                                               std::this_thread::get_id());
        Log(event->GetLogLevel(), event);
    }
#else
    void Logger::Debug(const std::string& payload, 
                       const std::string& file_name,
                       size_t line)
    {
        auto event = 
            std::make_shared<adalog::LogEvent>(payload, 
                                               logger_name_, 
                                               adalog::LogLevel::DEBUG, 
                                               std::chrono::system_clock::now(), 
                                               file_name, line, 
                                               std::this_thread::get_id());
        Log(event->GetLogLevel(), event);
    }

    void Logger::Info (const std::string& payload, 
                       const std::string& file_name,
                       size_t line)
    {
        auto event = 
            std::make_shared<adalog::LogEvent>(payload, 
                                               logger_name_, 
                                               adalog::LogLevel::INFO, 
                                               std::chrono::system_clock::now(), 
                                               file_name, line, 
                                               std::this_thread::get_id());
        Log(event->GetLogLevel(), event);
    }

    void Logger::Warn (const std::string& payload, 
                       const std::string& file_name,
                       size_t line)
    {
        auto event = 
            std::make_shared<adalog::LogEvent>(payload, 
                                               logger_name_, 
                                               adalog::LogLevel::WARN, 
                                               std::chrono::system_clock::now(), 
                                               file_name, line, 
                                               std::this_thread::get_id());
        Log(event->GetLogLevel(), event);
    }

    void Logger::Error(const std::string& payload, 
                       const std::string& file_name,
                       size_t line)
    {
        auto event = 
            std::make_shared<adalog::LogEvent>(payload, 
                                               logger_name_, 
                                               adalog::LogLevel::ERROR, 
                                               std::chrono::system_clock::now(), 
                                               file_name, line, 
                                               std::this_thread::get_id());
        Log(event->GetLogLevel(), event);
    }

    void Logger::Fatal(const std::string& payload, 
                       const std::string& file_name,
                       size_t line)
    {
        auto event = 
            std::make_shared<adalog::LogEvent>(payload, 
                                               logger_name_, 
                                               adalog::LogLevel::FATAL, 
                                               std::chrono::system_clock::now(), 
                                               file_name, line, 
                                               std::this_thread::get_id());
        Log(event->GetLogLevel(), event);
    }

#endif

    LoggerBuilder::LoggerBuilder()
    {
    }

    LoggerBuilder& LoggerBuilder::BuildLoggerName(const std::string& logger_name)
    {
        logger_name_ = logger_name;
        return *this;
    }

    LoggerBuilder& LoggerBuilder::BuildLogLevel(LogLevel::Value log_level)
    {
        log_level_ = log_level;
        return *this;
    }

    LoggerBuilder& LoggerBuilder::BuildAppender(LogAppender::Ptr appender)
    {
        appenders_.push_back(std::move(appender));
        return *this;
    }

    Logger::Ptr LoggerBuilder::BuildLogger()
    {
        auto logger = std::make_shared<Logger>(logger_name_, log_level_, appenders_);
        logger_name_.clear();
        appenders_.clear();
        return logger;
    }

    LoggerManager::LoggerManager()
    {
        auto builder = std::make_unique<LoggerBuilder>();
        builder->BuildLoggerName("default_logger");
        builder->BuildLogLevel(LogLevel::DEBUG);
        builder->BuildAppender(std::make_shared<StdOutAppender>());
        AddLogger(builder->BuildLogger());
    }

    LoggerManager& LoggerManager::GetInstance()
    {
        static LoggerManager manager;
        return manager;
    }

    Logger::Ptr LoggerManager::GetLogger(const std::string& logger_name)
    {
        auto it = logger_table_.find(logger_name);
        if (it == logger_table_.end())
            return {};
        return it->second;
    }

    void LoggerManager::AddLogger(Logger::Ptr logger)
    {
        logger_table_[logger->GetLoggerName()] = logger;
    }

} // namespace adalog